package com.hup.utils.io.zip;

import lombok.extern.log4j.Log4j2;
import org.apache.commons.compress.archivers.zip.ZipArchiveEntry;
import org.apache.commons.compress.archivers.zip.ZipFile;

import java.io.IOException;
import java.io.InputStream;
import java.util.Collections;
import java.util.Enumeration;
import java.util.zip.ZipEntry;

/**
 * 使用apache.commons.compress的zip工具类;
 * 解决java.zip只支持UTF-8编码的问题;
 * @author hugan
 * @date 2019/12/11
 */
@Log4j2
public class ArZipReader {

    public static int getTotalChild(String zipFullName) throws IOException {
        try (ZipFile zipFile = new ZipFile(zipFullName)) {
            return Collections.list(zipFile.getEntries()).size();
        }
    }

    /**
     * 以GBK编码解释zip文件,其他格式有需要再处理
     * @param path 文件的全路径
     */
    public static void readZip(String path, EntryHandler handler) {
        String curName = path;//当前处理的文件,用于异常时展示文件名
        try (ZipFile zipFile = new ZipFile(path, "GBK")) {
            //只能这样读取子文件数量,没有性能问题
            int total = Collections.list(zipFile.getEntries()).size();
            //log.info("total={}", total);
            handler.onGetTotal(total);

            int currentCount = 0;

            Enumeration<ZipArchiveEntry> entries = zipFile.getEntries();
            while (entries.hasMoreElements()) {
                currentCount++;
                ZipArchiveEntry ze = entries.nextElement();
                curName = ze.getName();
                //log.info("name={}, total={}", curName, ze.getSize());

                if (ze.isDirectory()) {
                    boolean isContinue = handler.onDirectory(currentCount, ze, curName);
                    if (!isContinue) break;
                } else {
                    InputStream ips = zipFile.getInputStream(ze);
                    boolean isContinue = handler.onFile(currentCount, ips, curName);
                    if (!isContinue) break;
                }
            }
            handler.onTaskEnd();
        } catch (Exception e) {
            handler.onException(curName, e);
        }
    }

    /**
     * 简单的读取某个子文件的数据
     * @param path zip文件
     * @param child 子文件名,包括扩展名,不含父级路径;不接受文件夹
     * @param handler 如果压缩包内,含多个同名文件(在不同目录),则只解析一个!如果找不到,则不会有异常或者回调;
     * 只要重写onFile,onException
     */
    public static void readZipChild(String path, String child, EntryHandler handler) {
        String curName = path;//当前处理的文件,用于异常时展示文件名
        try (ZipFile zipFile = new ZipFile(path, "GBK")) {
            //ZipArchiveEntry ze = zipFile.getEntry(curName);//这样要带父级路径]才能找到
            Enumeration<ZipArchiveEntry> entries = zipFile.getEntries();
            while (entries.hasMoreElements()) {
                ZipArchiveEntry ze = entries.nextElement();
                curName = ze.getName();
                if (curName.endsWith("/" + child) && !ze.isDirectory()) {
                    InputStream ips = zipFile.getInputStream(ze);
                    handler.onFile(1, ips, curName);
                    break;
                }
            }
        } catch (Exception e) {
            handler.onException(curName, e);
        }
    }

    public interface EntryHandler {
        /**
         * @param total zip内文件数量,含文件夹
         */
        default void onGetTotal(int total) {}

        /**
         * @param count 当前的文件index,从1开始
         * @param name 文件全名,含父路径(分隔符是'/')
         * @return isContinue
         */
        default boolean onDirectory(int count, ZipEntry entry, String name) throws IOException {
            return true;
        }

        /**
         * @param count 当前的文件index,从1开始
         * @param name 文件全名,含父路径(分隔符是'/')
         * @return isContinue
         */
        boolean onFile(int count, InputStream is, String name) throws IOException;

        /**
         * 遍历结束后
         */
        default void onTaskEnd() {
        }

        /**
         * @param curFileName 当前正在处理的文件名
         */
        void onException(String curFileName, Exception e);

    }

}
